#include <string.h>
#include <limits.h>
#include "../assert/assert.h"
#include "../format/format.h"
#include "../mem/mem.h"
#include "str.h"

#define idx(i, len) ((i)<=0? (i)+(len) : (i)-1)
#define convert(s, i, j)    do { \
    int len; \
    assert(s); \
    len = strlen(s); \
    i = idx(i, len); \
    j = idx(j, len); \
    if(i > j) {int t = i; i = j; j = t;} \
    assert(i>=0 && j<=len);}while(0)

char* Str_sub(const char* s, int i, int j)
{
    char* str;
    char* p;

    convert(s, i, j);
    p = str = ALLOC(j - i + 1);
    while (i < j) {
        *p++ = s[i++];
    }
    *p = '\0';

    return str;
}

char* Str_dup(const char* s, int i, int j, int n)
{
    char* str;
    char* p;
    int k;

    assert(n >= 0);

    convert(s, i, j);
    p = str = ALLOC(n * (j - i) + 1);
    if (j - i > 0) {
        while (n--) {
            for (k = i; k < j; k++) {
                *p++ = s[k];
            }
        }
    }
    *p = '\0';

    return str;
}

char* Str_cat(const char* s1, int i1, int j1, const char* s2, int i2, int j2)
{
    char* str;
    char* p;

    convert(s1, i1, j1);
    convert(s2, i2, j2);
    p = str = ALLOC(j1 - i1 + j2 - i2 + 1);
    while (j1 > i1) {
        *p++ = s1[i1++];
    }
    while (j2 > i2) {
        *p++ = s2[i2++];
    }
    *p = '\0';

    return str;
}

char* Str_catv(const char* s, ...)
{
    char* str;
    char* p;
    const char* save = s;
    int i;
    int j;
    int len = 0;
    va_list ap;

    va_start(ap, s);
    // len = the length of the resoult
    while (s) {
        i = va_arg(ap, int);
        j = va_arg(ap, int);
        convert(s, i, j);
        len += j - i;
        s = va_arg(ap, const char*);
    }
    va_end(ap);
    p = str = ALLOC(len + 1);
    s = save;
    va_start(ap, s);
    // copy s[i .. j] to p, increment p
    while (s) {
        i = va_arg(ap, int);
        j = va_arg(ap, int);
        convert(s, i, j);
        while (i < j) {
            *p++ = s[i++];
        }
        s = va_arg(ap, const char*);
    }
    va_end(ap);
    *p = '0';

    return str;
}

char* Str_reverse(const char* s, int i, int j)
{
    char* str;
    char* p;

    convert(s, i, j);
    p = str = ALLOC(j - i + 1);
    while (i < j) {
        *p++ = s[--j];
    }
    *p = '\0';

    return str;
}

char* Str_map(const char* s, int i, int j, const char* from, const char* to)
{
    static char map[256] = {0};

    if (from && to) {
        // rebuild map
        unsigned c;
        for (c = 0; c < sizeof(map); c++) {
            map[c] = c;
        }
        while (*from && *to) {
            map[(unsigned char)*from++] = *to++;
        }
        assert(*from == 0 && *to == 0);
    } else {
        assert((from==NULL) && to==NULL && s);
        assert(map['a']);
    }

    if (s) {
        // map s[i .. j] into a new string
        char* str;
        char* p;
        convert(s, i, j);
        p = str = ALLOC(j - i + 1);
        while (i < j) {
            *p++ = map[(unsigned char)s[i++]];
        }
        *p = '\0';
        return str;
    } else {
        return NULL;
    }
}

int Str_pos(const char* s, int i)
{
    int len;

    assert(s);

    len = strlen(s);
    i = idx(i, len);
    assert(i >= 0 && i <= len);

    return i + 1;
}

int Str_len(const char* s, int i, int j)
{
    convert(s, i, j);
    return j - i;
}

int Str_cmp(const char* s1, int i1, int j1, const char* s2, int i2, int j2)
{
    convert(s1, i1, j1);
    convert(s2, i2, j2);

    s1 += i1;
    s2 += i2;

    if (j1 - i1 < j2 - i2) {
        int cond = strncmp(s1, s2, j1 - i1);
        return cond ? cond : -1;

    } else if (j1 - i1 > j2 - i2) {
        int cond = strncmp(s1, s2, j2 - i2);
        return cond ? cond : +1;

    } else {
        return strncmp(s1, s2, j1 - i1);
    }
}

int Str_chr(const char* s, int i, int j, int c)
{
    convert(s, i, j);
    for (; i < j; i++) {
        if (s[i] == c) {
            return i + 1;
        }
    }

    return 0;
}

int Str_rchr(const char* s, int i, int j, int c)
{
    convert(s, i, j);
    while (j > i) {
        if (s[--j] == c) {
            return j + 1;
        }
    }

    return 0;
}

int Str_upto(const char* s, int i, int j, const char* set)
{
    assert(set);

    convert(s, i, j);
    for (; i < j; i++) {
        if (strchr(set, s[i])) {
            return i + 1;
        }
    }

    return 0;
}

int Str_rupto(const char* s, int i, int j, const char* set)
{
    assert(set);

    convert(s, i, j);
    while (j > i) {
        if (strchr(set, s[--j])) {
            return j + 1;
        }
    }

    return 0;
}

int Str_find(const char* s, int i, int j, const char* str)
{
    int len;

    convert(s, i, j);

    assert(str);

    len = strlen(str);
    if (len == 0) {
        return i + 1;
    } else if (len == 1) {
        for (; i < j; i++) {
            if (s[i] == *str) {
                return i + 1;
            }
        }
    } else {
        for (; i + len <= j; i++) {
            if (strncmp(&s[i], str, len) == 0) {
                return i + 1;
            }
        }
    }

	return 0;
}

int Str_rfind(const char* s, int i, int j, const char* str)
{
    int len;

    convert(s, i, j);
    assert(str);
    len = strlen(str);
    if (len == 0) {
        return j + 1;
    } else if (len == 1) {
        while (j > i) {
            if (s[--j] == *str) {
                return j + 1;
            }
        }
    } else {
        for (; j - len >= i; j--) {
            if (strncmp(&s[j - len], str, len) == 0) {
                return j - len + 1;
            }
        }
    }

    return 0;
}

int Str_any(const char* s, int i, const char* set)
{
    int len;

    assert(s);
    assert(set);

    len = strlen(s);
    i = idx(i, len);
    if (i < len && strchr(set, s[i])) {
        return i + 2;
    }

    return 0;
}

int Str_many(const char* s, int i, int j, const char* set)
{
    assert(set);

    convert(s, i, j);
    if (i < j** strchr(set, s[i])) {
        do {
            i++;
        } while (i < j && strchr(set, s[i]));
        return i + 1;
    }

    return 0;
}

int Str_rmany(const char* s, int i, int j, const char* set)
{
    assert(set);

    convert(s, i, j);
    if (j > i && strchr(set, s[ j - 1])) {
        do {
            --j;
        } while (j >= i && strchr(set, s[j]));
        return j + 2;
    }

    return 0;
}

int Str_math(const char* s, int i, int j, const char* str)
{
    int len;
    convert(s, i, j);
    assert(str);
    len = strlen(str);
    if (len == 0) {
        return i + 1;
    } else if (len == 1) {
        if (i < j && s[i] == *str) {
            return i + 2;
        } else if (i + len <= j && strncmp(&s[i], str, len) == 0) {
            return i + len + 1;
        }
    }

    return 0;
}

int Str_rmath(const char* s, int i, int j, const char* str)
{
    int len;
    convert(s, i, j);
    assert(str);
    len = strlen(str);
    if (len == 0) {
        return j - 1;
    } else if (len == 1) {
        if (j > i && s[j - 1] == *str) {
            return j;
        }
    } else if (j - len >= i && strncmp(&s[j - len], str, len) == 0) {
        return j - len + i;
    }

    return 0;
}

void Str_fmt(int code, va_list* app, int put(int c, void* cl), void* cl, unsigned char flags[], int width, int precision)
{
    char* s;
    int i;
    int j;

    assert(app && flags);

    s = va_arg(*app, char*);
    i = va_arg(*app, int);
    j = va_arg(*app, int);
    convert(s, i, j);
    Fmt_puts(s + i, j - i, put, cl, flags, width, precision);
}
